﻿using Newtonsoft.Json.Linq;
using Sidvall.Collections;
using Sidvall.Net;
using Sidvall.Serialization;
using System.Threading.Tasks;

namespace Sidvall.Security
{
    public class AuthenticationProvider : IAuthenticationProvider
    {
        #region Public Members

        #region GetAuthenticationClient

        public IAuthenticationClient GetAuthenticationClient(System.Uri address, string clientId, string clientSecret, ClientAuthenticationStyle authenticationStyle)
        {
            var settings = new HttpClientSettings();
            if (address != null)
                settings.BaseAddress = address.ToString();
            settings.LanguagePolicy = LanguagePolicy.None;
            settings.ResponseTypes.Clear();
            switch (authenticationStyle)
            {
                case ClientAuthenticationStyle.BasicAuthentication:
                    settings.AuthenticationHeader = AuthenticationHeaderManager.NewBasicHeader(clientId, clientSecret);
                    break;
                case ClientAuthenticationStyle.PostValues:
                    break;
                default:
                    authenticationStyle = ClientAuthenticationStyle.None;
                    break;
            }
            var client = Sidvall.SystemContext.Current.HttpClientFactory.GetHttpClient(settings);
            return new AuthenticationClient(client, address, clientId, clientSecret, authenticationStyle);
        }

        #endregion
        #region GetUserInfoAsync

        public async Task<UserInfo> GetUserInfoAsync(string authorityUserInfoEndpoint, string accessToken)
        {
            var authenticationHeader = AuthenticationHeaderManager.NewBearerHeader(accessToken);
            var settings = new Sidvall.Net.HttpClientSettings()
            {
                BaseAddress = authorityUserInfoEndpoint.Trim('/') + "/",
                AuthenticationHeader = authenticationHeader,
            };
            var client = Sidvall.SystemContext.Current.HttpClientFactory.GetHttpClient(settings);
            var response = await client.GetAsync(string.Empty).ConfigureAwait(false);
            response.EnsureSuccessStatusCode();
            var json = await response.GetContentAsStringAsync().ConfigureAwait(false);
            var item = JObject.Parse(json);
            var userInfo = new UserInfo();
            foreach (var value in item)
            {
                if (value.Key.Equals(ClaimTypes.Subject, System.StringComparison.OrdinalIgnoreCase))
                    userInfo.Sub = value.Value.ToString();
                else
                    userInfo.Items.Add(value.Key, value.Value.ToString());
            }
            return userInfo;
        }

        #endregion
        #region ParseAuthorizeResponseFromAddress

        public AuthorizeResponse ParseAuthorizeResponseFromAddress(string address)
        {
            AuthorizeResponse authorizeResponse;
            string[] fragments, qparams, parts;

            authorizeResponse = new AuthorizeResponse();
            if (string.IsNullOrWhiteSpace(address))
            {
                authorizeResponse.ResponseType = Security.AuthorizeResponseType.Error;
                return authorizeResponse;
            }
            if (address.Contains("#"))
            {
                // fragment encoded
                fragments = address.Split('#');
                authorizeResponse.ResponseType = Security.AuthorizeResponseType.Token;
            }
            else if (address.Contains("?"))
            {
                // query string encoded
                fragments = address.Split('?');
                authorizeResponse.ResponseType = Security.AuthorizeResponseType.AuthorizationCode;
            }
            else
            {
                // form encoded
                fragments = new string[] { "", address };
                authorizeResponse.ResponseType = Security.AuthorizeResponseType.FormPost;
            }
            if (address.Contains(AuthenticationConstants.Error))
            {
                authorizeResponse.ResponseType = Security.AuthorizeResponseType.Error;
            }
            qparams = fragments[1].Split('&');
            foreach (var param in qparams)
            {
                parts = param.Split('=');
                if (parts.Length == 2)
                {
                    if (string.IsNullOrWhiteSpace(parts[0]))
                        continue;
                    if (parts[0].Equals(AuthenticationConstants.Code, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.Code = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.AccessToken, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.AccessToken = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.IdentityToken, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.IdentityToken = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.Error, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.Error = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.Scope, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.Scope = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.TokenType, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.TokenType = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.State, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.State = parts[1];
                    else if (parts[0].Equals(AuthenticationConstants.ExpiresIn, System.StringComparison.OrdinalIgnoreCase))
                        authorizeResponse.ExpiresIn = parts[1].ToInt64(0);
                }
                else
                {
                    throw new System.InvalidOperationException("Malformed callback URL.");
                }
            }
            return authorizeResponse;
        }

        #endregion
        #region ParseIdentityTokenFromToken

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public IdentityToken ParseIdentityTokenFromToken(string token)
        {
            if (string.IsNullOrWhiteSpace(token))
                return null;
            var parts = token.Split('.');
            if (parts.Length != 3)
                return null;
            var value = Decode(parts[1]);
            var part = System.Text.Encoding.UTF8.GetString(value, 0, value.Length);
            var item = Newtonsoft.Json.Linq.JObject.Parse(part);
            var identityToken = new IdentityToken()
            {
                Subject = item.ValueToString(ClaimTypes.Subject),
            };
            var authenticationMethods = item.ValueToStrings(ClaimTypes.AuthenticationMethodReference);
            if (authenticationMethods != null)
            {
                identityToken.AuthenticationMethods.AddRange(authenticationMethods);
            }
            else
            {
                var authenticationMethod = item.ValueToString(ClaimTypes.AuthenticationMethodReference);
                if (!string.IsNullOrWhiteSpace(authenticationMethod))
                    identityToken.AuthenticationMethods.Add(authenticationMethod);
            }
            return identityToken;
        }

        #endregion

        #region ToString

        public override string ToString()
        {
            return "AuthenticationProvider - Sidvall.Security.Authentication";
        }

        #endregion

        #endregion
        #region Private Members

        #region Decode

        private static byte[] Decode(string arg)
        {
            string s = arg;
            s = s.Replace('-', '+'); // 62nd char of encoding 
            s = s.Replace('_', '/'); // 63rd char of encoding 
            switch (s.Length % 4) // Pad with trailing '='s 
            {
                case 0:
                    break; // No pad chars in this case 
                case 2:
                    s += "=="; break; // Two pad chars 
                case 3:
                    s += "="; break; // One pad char 
                default:
                    throw new System.ArgumentException("Illegal base64url string!");
            }
            return System.Convert.FromBase64String(s); // Standard base64 decoder 
        }

        #endregion
        #region EncodeCredential

        private static string EncodeCredential(string userName, string password)
        {
            System.Text.Encoding encoding = System.Text.Encoding.UTF8;
            string credential = System.String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0}:{1}", userName, password);
            return System.Convert.ToBase64String(encoding.GetBytes(credential));
        }

        #endregion

        #endregion
    }
}
